package robocup.component;

import java.util.LinkedList;

import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.actions.Action;
import robocup.component.actions.Action.TYPE;
import robocup.component.geometry.Vektor;
import robocup.component.infotypes.AuralInfo;
import robocup.component.infotypes.ChangePlayerTypeInfo;
import robocup.component.infotypes.CoachSayInfo;
import robocup.component.infotypes.DynamicObjectInfo;
import robocup.component.infotypes.Info;
import robocup.component.infotypes.InfoReceiver;
import robocup.component.infotypes.PlayModeInfo;
import robocup.component.infotypes.PlayerSayInfo;
import robocup.component.infotypes.PlayerTypesInfo;
import robocup.component.infotypes.RefereeInfo;
import robocup.component.infotypes.SenseBodyInfo;
import robocup.component.infotypes.ServerParamInfo;
import robocup.component.infotypes.VisualInfo;
import robocup.component.speechacts.MessageFactory;
import robocup.component.speechacts.MessageFactory.MESSAGES;
import robocup.component.speechacts.coachlanguage.TypeConstants;
import robocup.component.util.comparators.PlayerComparator.DIMENSION;
import robocup.component.util.comparators.PlayerComparator.ORDER;
import robocup.component.worldmodel.BallModel;
import robocup.component.worldmodel.CLangModel;
import robocup.component.worldmodel.CollisionModel;
import robocup.component.worldmodel.LongTermMemory;
import robocup.component.worldmodel.MeModel;
import robocup.component.worldmodel.PlayModeModel;
import robocup.component.worldmodel.PlayersModel;
import robocup.component.worldmodel.RoleModel;
import robocup.component.worldmodel.ShortTermMemory;
import robocup.component.worldmodel.ViewModel;
import robocup.component.worldmodel.PlayersModel.DOMINATED_SIDE;
import robocup.component.worldmodel.ShortTermMemory.VEKTORS;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.DynamicObject;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.StaticObject;
import robocup.component.worldobjects.Player.ROLE;
import robocup.connection.TeamSoccerScopeMediator;

/**
 * 
 * This class contains all knowledge about the current world state. It is
 * updated each time an info arrives from the SServer.
 * 
 * The world model is divided into different submodels:
 * 
 * MeModel - a model containing all knowledge about the player (position, speed,
 * etc.) BallModel - a model containing all knowledge about the ball
 * PlayersModel - a model containing all knowledge about other Players
 * CollisionModel - a model that determines wether we collided or not (if we
 * collided some of the informations have to be interpreted a different way)
 * PlayModeModel - a model containing all knowledge about the last play modes
 * and referee messages NeckRotator - a model containing all knowledge about
 * what directions were seen CLangModel - a model containing all knowledge about
 * infos sent from the coach
 * 
 * @author rw, smarqoo, wild, till, haeder
 */
public class WorldModel {

  // Parent RobocupAgent
  private final RobocupAgent    myMaster;

  // all possible player configs
  private PConf           configs[]        = null;

  // the models

  private final MeModel         me;

  private final BallModel       ball;

  private final PlayersModel    players;

  private final CollisionModel  collisionModel;

  private final PlayModeModel   playModeModel;
  
  private final ViewModel       viewModel;

//  private NeckRotator     neckRotator      = null;

  private final RoleModel       roleModel;

  private final CLangModel      clangModel;

  // an array containing all models
  private final InfoReceiver[]        models;

  // a memory used as cache (reseted after each cycle)
  private final ShortTermMemory shortTermMemory  = new ShortTermMemory();

  // a memeory storing long term data
  private final LongTermMemory  longTermMemory;

  // a reference to the player
  private final Player          self = Player.getDummyInstance();

  // the message factory used for decoding arriving say messages
  private final MessageFactory  mf;

  // number of sense body infos
  private int             bodyCycleCounter = 0;

  // world is based on a VI (not on a BS)
  private boolean         viBased          = false;

  // goals scored we
  private int             goalsTeam        = 0;

  // goals scored other
  private int             goalsOther       = 0;

  // the last cycle we moved
  private int             lastMoveCycle    = -1;

  // a counter for all move actions
  // used for telling if a move action was successful
  private int             moveCount        = 0;

  // the opponents teamname
  private String          opponentTeamName = "";

  // a dummy vektor
  private final Vektor          dummyVektor      = new Vektor();

  private boolean initialized = false;

//  private int             teamFormation;

  /**
   * Default constructor.
   * 
   * Initializes all the submodels.
   * 
   * @param agent -
   *          the agent object, to whom the world-model belongs (parent)
   */
  public WorldModel(
      final RobocupAgent agent) {

    this.myMaster = agent;
    this.longTermMemory = new LongTermMemory(this);
    
    this.self.setPConf(new PConf());
    
    this.playModeModel = new PlayModeModel(this);
    this.me = new MeModel(this);
    this.ball = new BallModel(this);
    this.players = new PlayersModel(this);
    this.collisionModel = new CollisionModel(this);
    this.roleModel = new RoleModel(this, this.myMaster.getProphet());
    this.clangModel = new CLangModel(this);
    this.viewModel= new ViewModel(this);

    this.models = new InfoReceiver[] { this.playModeModel, this.me, this.ball, this.players, this.collisionModel, this.roleModel, this.clangModel, this.viewModel };

    this.mf = new MessageFactory();
  } // end of constructor WorldModel(FactBase iFactBase)

  /**
   * updates the world.
   * 
   * called from the RobocupAgent each time an info arrives.
   * 
   * @param info -
   *          new info
   */
  public synchronized void update(final Info info) {

    if (info == null) {
      System.out.println("WM.update(): called without info.");
      return;
    }

    if(!initialized && info.getInfoType() != Info.TYPE_INIT){
      return;
    }
    
    // reset the shortTermMemory
    this.shortTermMemory.resetAll(info.getInfoType());

    // insert info in longTermMemory
    this.longTermMemory.insertInfo(info);
        
    // depending on the info type there is a different
    // update logic
    switch (info.getInfoType()) {

      // INIT
      case Info.TYPE_INIT:
        this.self.setNumber(this.myMaster.getNumber());
        this.self.setGoalie(this.myMaster.isGoalie());
        this.self.setReliable(true);
        
        this.me.initSelf(this.self);
        this.players.initSelf(this.self);
        this.initialized  = true;
        break;

      // SENSE
      case (Info.TYPE_SENSE):
        this.bodyCycleCounter++;

        final SenseBodyInfo sbInfo = (SenseBodyInfo) info;

        // did our moe action succeed?
        if (this.moveCount != sbInfo.getCount(TYPE.MOVE)) {
          this.lastMoveCycle = info.getCycle();
          this.moveCount = sbInfo.getCount(TYPE.MOVE);
        }

        this.viBased = false;

        // forward the info to all submodels
        for (final InfoReceiver model : this.models) {
          model.update(sbInfo);
        }
        break;

      // VISUAL
      case Info.TYPE_VISUAL:

        this.viBased = true;

        final VisualInfo vInfo = (VisualInfo) info;

        // forward the info to all submodels
        for (final InfoReceiver model : this.models) {
          model.update(vInfo);
        }
        break;

      // AURAL
      case Info.TYPE_AURAL:

        final AuralInfo aInfo = (AuralInfo) info;

        if (aInfo instanceof PlayModeInfo) {

          final PlayModeInfo pmInfo = (PlayModeInfo) aInfo;

          // if the playMode changes to goal_? -> add the goal
          if (pmInfo.getPlayModeType() == PLAY_MODE.GOAL_OTHER && this.playModeModel
              .getLastPlayModeMessageType() != PLAY_MODE.GOAL_OTHER) {
            this.goalsOther++;
          }
          else if (pmInfo.getPlayModeType() == PLAY_MODE.GOAL_OWN && this.playModeModel
              .getLastPlayModeMessageType() != PLAY_MODE.GOAL_OWN) {
            this.goalsTeam++;
          }

          // forward the info to all submodels
          for (final InfoReceiver model : this.models) {
            model.update(pmInfo);
          }
        }
        else if (aInfo instanceof RefereeInfo) {
          final RefereeInfo rInfo = ((RefereeInfo) aInfo);

          // forward the info to all submodels
          for (final InfoReceiver model : this.models) {
            model.update(rInfo);
          }
        }

        break;

      // AURAL - Player Say Info
      case Info.TYPE_AURAL_PSI:

        // decode psi once and forward it to submodels
        final PlayerSayInfo psiInfo = ((PlayerSayInfo) info);
        this.mf.generateMessages(psiInfo.getMessage(), this.getBodyCycle());
        for (final InfoReceiver model : this.models) {
          model.update(psiInfo);
        }

        // forward the psi to the gui (if running)
        if (this.myMaster.useGui) {
          int pIndex = this.getNumber() - 1;
          if (this.myMaster.isTeamEast) {
            pIndex += 11;
          }
          final StringBuffer strBuff = new StringBuffer();
          final Player p = this.getPlayerByNumber(psiInfo.getNumber(), true);
          ROLE role = null;
          if (p != null) {
            role = p.getDefaultRole();
          }
          strBuff.append(role).append(": ");
          for (final MESSAGES m : this.mf.getMessageTypes()) {
            strBuff.append(m);

            // only use this if you don't care about performance
            if (m == MESSAGES.COMPLETE_PLAYER) {
              this.mf.decodeMessageNumber(0);
              strBuff.append(this.mf.getNumber());
            }
            strBuff.append(",");
          }
          TeamSoccerScopeMediator.soccerScopeAddHearMessageToScene(pIndex,
              new StringBuffer().append(psiInfo.getNumber()).append("-")
                  .append(strBuff).toString());
        }
        break;

      // AURAL - Coach Say Info
      case Info.TYPE_AURAL_CSI:
        final CoachSayInfo csi = (CoachSayInfo) info;

        final TypeConstants.MESSAGETYPE type = csi.getMessageType();

        switch (type) {

          case FREEFORM:
            this.mf.generateMessages(csi.getFreeformMessage(), this
                .getBodyCycle());
            // update the mental-model as well
            this.players.update(csi);
            this.ball.update(csi);

            // forward the csi to the gui (if running)
            if (this.myMaster.useGui) {
              final StringBuffer strBuff = new StringBuffer();
              strBuff.append("TRAINER: ");
              for (final MESSAGES m : this.mf.getMessageTypes()) {
                strBuff.append(m + " ");
              }
              int pIndex = this.getNumber() - 1;
              if (this.myMaster.isTeamEast) {
                pIndex += 11;
              }
              TeamSoccerScopeMediator.soccerScopeAddHearMessageToScene(pIndex,
                  new StringBuffer().append("0-").append(strBuff).toString());
            }

            break;

          default:
            this.clangModel.update(csi);
            break;
        }

        break;

      // not used (now part of Info.TYPE_PLAYERTYPE)
      case Info.TYPE_PPARAM:
        break;

      // set ServerParam
      case Info.TYPE_SPARAM:
        SConf.setInstance(((ServerParamInfo) info).getSConf());
        break;

      // PlayerType: get all avaiable PlayerTypes (7 by default)
      case Info.TYPE_PTYPE:
        if (this.configs == null) {
          final PlayerTypesInfo pti = (PlayerTypesInfo) info;
          if (pti.playerTypesFull()) {
            this.configs = pti.getPlayerTypes();
          }

        }
        break;

      // ChangePlayerType: sets teammates and the own PConf by PlayerType
      case Info.TYPE_PLAYERTYPE:
        final Player[] team = this.getTeammates();
        final Player[] next = this.getNextPlayers();
        final Player[] ecpt = this.getExpectedPlayers();
        final ChangePlayerTypeInfo cpti = (ChangePlayerTypeInfo) info;
        if (this.configs != null) {
          team[cpti.getPlayerNumber() - 1].setPConf(this.configs[cpti
              .getPlayerType()]);
          team[cpti.getPlayerNumber() - 1].setStatusChanged(true);
          team[cpti.getPlayerNumber() - 1]
              .setRecovery(SConf.getInstance().RECOVER_MAX);
          team[cpti.getPlayerNumber() - 1].setEffort(this.configs[cpti
              .getPlayerType()].EFFORT_MAX);
          team[cpti.getPlayerNumber() - 1]
              .setStamina(SConf.getInstance().STAMINA_MAX);
          
          ecpt[cpti.getPlayerNumber() - 1].setPConf(this.configs[cpti
             .getPlayerType()]);
          ecpt[cpti.getPlayerNumber() - 1].setStatusChanged(true);
          ecpt[cpti.getPlayerNumber() - 1]
             .setRecovery(SConf.getInstance().RECOVER_MAX);
          ecpt[cpti.getPlayerNumber() - 1].setEffort(this.configs[cpti
             .getPlayerType()].EFFORT_MAX);
          ecpt[cpti.getPlayerNumber() - 1]
             .setStamina(SConf.getInstance().STAMINA_MAX);          
          
          next[cpti.getPlayerNumber() - 1].setPConf(this.configs[cpti
             .getPlayerType()]);
          next[cpti.getPlayerNumber() - 1].setStatusChanged(true);
          next[cpti.getPlayerNumber() - 1]
             .setRecovery(SConf.getInstance().RECOVER_MAX);
          next[cpti.getPlayerNumber() - 1].setEffort(this.configs[cpti
             .getPlayerType()].EFFORT_MAX);
          next[cpti.getPlayerNumber() - 1]
             .setStamina(SConf.getInstance().STAMINA_MAX);
        }
        break;

      // ChangeOpponentTypeInfo: In this case, the opponent team changed
      // Player
      case Info.TYPE_OPLAYERTYPE:
//        final Player[] opponents = this.getOpponents();
//        final ChangeOpponentTypeInfo coti = (ChangeOpponentTypeInfo) info;
//        for (int i = 0; i < opponents.length; i++) {
//          if (opponents[i].getNumber() == coti.getPlayerNumber()) {
//            opponents[i].setPConf(this.configs[0]);
//            opponents[i].setStatusChanged(true);
//          }
//        }

        break;
    }
  } // end of method update

  /**
   * returns the shortTermMemory
   * 
   * @return WM.shortTermMemory
   */
  public ShortTermMemory getShortTermMemory() {

    return this.shortTermMemory;
  }

  /**
   * @return Returns the longTermMemory.
   */
  public LongTermMemory getLongTermMemory() {

    return this.longTermMemory;
  }

  /**
   * The current cycle.
   * 
   * @return
   */
  public final int getCycle() {

    if (this.getSelfRef() == null) {
      return -1;
    }
    return this.getSelfRef().getCycle();
  }

  /**
   * returns the player in kick distance to the ball.
   * 
   * @return
   * 
   * TODO IMPROVE!
   */
  public Player getPlayerWithBall() {

    for (final Player p : this.getExpectedPlayers()) {
      if (p.getDistance(this.getBallRef()) < 1.0) {
        return p;
      }
    }
    return null;
  }

  /**
   * getMyPos returns the current position of the player
   * 
   * @return The player's position. Note that if you perform operations on this
   *         Vektor that change it, you need to clone it first!
   */
  public final Vektor getMyPosition() {

    return this.me.getPosition();
  } // end of method getMyPosition()

  /**
   * the age of an dynamic object.
   * 
   * @param o -
   *          object
   * @return time in senseBody steps since the object was last seen (or
   *         created).
   * 
   * FIXME use DynObject.getAge instead
   */
  public int getAge(final DynamicObject o) {

    if (o.getBodyCycle() > this.getBodyCycle()) {
      System.out
          .println("WM.getAge(DO) called with wrong current Cycle. C-WM:" + this
              .getBodyCycle() + " C-DO:" + o.getCycle());
    }
    return this.getBodyCycle() - o.getBodyCycle();
  }

  /**
   * returns the cycles since we have seen a given point
   * 
   */
  public double getAgeOfPoint(final Vektor p) {

    return this.viewModel.getAgeOfPoint(p);
  }

  /**
   * the cycles since we have seen to a given direction
   * 
   * @param dir -
   *          abs dir
   * @return time since the direction was last seen
   */
  public double getAgeOfDir(final double dir) {

    return this.viewModel.getAgeOfDir(dir);
  }

  /**
   * @return true if we play on the left side.
   */
  public boolean isTeamEast() {

    return this.myMaster.isTeamEast();
  }

  /**
   * @return Ball - the current ball (REF!!)
   */
  public final Ball getBallRef() {

    return this.ball.getBallRef();
  } // end of method getBall()

  /**
   * This method calculates if the player is in an position to kick the ball.
   * Note that due to noise it is possible that this method returns true
   * although the player cannot kick the ball.
   * 
   * In fact this method checks if the ball is located in the circle spanned by
   * the radius of the player, the kickable margin and the radius of the ball.
   * 
   * @return true if the specified player is in a position to kick the ball.
   */
  public final boolean canKick() {

    return this.getSelfRef().canKick(this.getBallRef());
  } // end of method canKick()

  /**
   * This method calculates if the player is in an position to tackle the ball.
   * Note that due to noise it is possible that this method returns true
   * although the player cannot tackle the ball.
   * 
   * In fact this method checks if the ball is located in the circle spanned by
   * the radius of the player, the tackle distance and the radius of the ball.
   * 
   * @return true if the specified player is in a position to tackle the ball.
   * 
   * FIXME move to Player
   */
  public final boolean canTackle() {

    if (this.getSelfRef().getTackledeadCountdown() > 0) {
      return false;
    }

    if (this.getSelfRef().tackleSuccessProbability(
        this.getBallRef().getPosition()) > 0) {
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Checks whether this player is in own half or not.
   * 
   * @return true, if this player is in own half, false otherwise
   */
  public final boolean inOwnHalfMe() {

    return this.inOwnHalf(this.getSelfRef());
  }

  /**
   * Checks whether a player is in own half.
   * 
   * @param player
   *          to player to check
   * @return true, if the player is in own half
   */
  public final boolean inOwnHalf(final Player player) {

    return this.inOwnHalf(player, player.getPConf().PLAYER_SIZE);
  }

  /**
   * Checks whether a ball is in own half.
   * 
   * @param ball
   *          the ball to check
   * @return true if the ball is sure in own half
   */
  public final boolean inOwnHalf(final Ball ball) {

    return this.inOwnHalf(ball, SConf.getInstance().BALL_SIZE);
  }

  /**
   * Checks whether a object is in own half.
   * 
   * @param dynobj
   *          the object to check
   * @param size
   *          the size of the object
   * @return true if the object is sure in own half
   */
  public final boolean inOwnHalf(final DynamicObject dynobj,
      final double size) {

    return dynobj.getPosition().y + size < 0;
  }

  /**
   * inPenalty returns true, if the player is in the penalty area of his team's
   * side
   * 
   * @return boolean - true if the player is in the his team's penalty area,
   *         false otherwise
   */
  public final boolean inPenalty() {

    return this.inOwnPenaltyArea(this.getSelfRef());
  } // end of method inPenalty()

  /**
   * inOwnPenaltyArea returns true if the DynamicObject with radius size is in
   * own penalty area, false otherwise.
   * 
   * @param dynobj -
   *          the object
   * @param size -
   *          radius of the DynamicObject
   * @return true if the DynamicObject is in own penalty area, false otherwise
   */
  public final boolean inOwnPenaltyArea(final DynamicObject dynobj,
      final double size) {

    return this.inOwnPenaltyArea(dynobj.getPosition().x,
        dynobj.getPosition().y, size);
  } // end of method inOwnPenaltyArea()

  /**
   * <code>inOwnPenaltyArea</code> checks if an Object with the coordinates
   * <code>x</code> & <code>y</code> and radius <code>size</code> is in
   * the penalty area, of the player's team. (Also used in getInterceptionPoint)
   * 
   * @param x -
   *          X-coordinate
   * @param y -
   *          Y-coordinate
   * @param size -
   *          radius of the object
   * @return boolean - true if the Object is in own penalty area, false
   *         otherwise
   */
  public final boolean inOwnPenaltyArea(final double x,
      final double y,
      final double size) {

    if ((x + size < -SConf.getInstance().half_penalty_width) || (SConf
        .getInstance().half_penalty_width < x - size) || (y + size < -SConf
        .getInstance().half_length) || (-SConf.getInstance().half_length + SConf
        .getInstance().penalty_length < y - size)) {
      return false;
    }

    return true;
  }

  /**
   * @param b -
   *          the ball
   * @return boolean - true if the ball is in own penalty area, else false
   */
  public final boolean inOwnPenaltyArea(final Ball b) {

    return this.inOwnPenaltyArea(b, SConf.getInstance().BALL_SIZE);
  }

  /**
   * @param player -
   *          the player
   * @return boolean - true if player is in own penalty area, else false
   */
  public boolean inOwnPenaltyArea(final Player player) {

    return this.inOwnPenaltyArea(player, player.getPConf().PLAYER_SIZE);
  }

  /**
   * Checks if the player is in the opponents' penalty area.
   * 
   * @return true, if this player is in opponents' penalty area, false otherwise
   */
  public final boolean inOpponentsPenaltyMe() {

    return this.inOpponentsPenalty(this.getSelfRef());
  }

  /**
   * Checks if a player is in opponents' penalty area.
   * 
   * @param player
   *          the player to check
   * @return true, if the player is opponents' penalty area, false otherwise
   * 
   * FIXME move to SConf
   */
  public boolean inOpponentsPenalty(final Player player) {

    return this.inOpponentsPenalty(player, player.getPConf().PLAYER_SIZE);
  }

  /**
   * Checks if a ball is in opponents' penalty area.
   * 
   * @param ball
   *          the ball to check
   * @return true, if the ball is opponents' penalty area, false otherwise
   * 
   * FIXME move to SConf
   */
  public boolean inOpponentsPenalty(final Ball ball) {

    return this.inOpponentsPenalty(ball, SConf.getInstance().BALL_SIZE);
  }

  /**
   * Checks if an object is in opponents' penalty area.
   * 
   * @param dynobj
   *          the object to check
   * @param size
   *          the size of the object
   * @return true, if the object is in opponents' penalty area
   * 
   * FIXME move to SConf
   */
  public boolean inOpponentsPenalty(final DynamicObject dynobj,
      final double size) {

    return this.inOpponentsPenalty(dynobj.getPosition().x,
        dynobj.getPosition().y, size);
  }

  /**
   * Checks if an object is in opponents' penalty area.
   * 
   * @param dynobj
   *          the object to check
   * @param size
   *          the size of the object
   * @return true, if the object is in opponents' penalty area
   * 
   * FIXME move to SConf
   */
  public boolean inOpponentsPenalty(final Vektor point) {

    return this.inOpponentsPenalty(point.x, point.y, 0);
  }

  /**
   * Checks if an object is in opponents' penalty area.
   * 
   * @param x
   *          the X-coordinate of the object
   * @param y
   *          the Y-coordinate of the object
   * @param size
   *          the size of the object
   * @return true, if the object is in opponents' penalty area, false otherwise
   * 
   * FIXME move to SConf
   */
  public boolean inOpponentsPenalty(final double x,
      final double y,
      final double size) {

    if ((y + size) < (SConf.getInstance().half_length - SConf.getInstance().penalty_length) // in
                                                                                            // front
                                                                                            // of
                                                                                            // penalty
                                                                                            // area
        || (Math.abs(x) + size > SConf.getInstance().half_penalty_width) // besides
                                                                          // penalty
                                                                          // area
        || (y + size > SConf.getInstance().half_length)) {
      return false;
    }

    return true;
  }

  /**
   * Checks whether this player is in midfield or not.
   * 
   * @return true, if player is in midfield, false otherwise
   * 
   * FIXME move to Formation
   */
  public final boolean inMidfieldMe() {

    return this.inMidfield(this.getSelfRef().getPosition().y, this.getSelfRef()
        .getPConf().PLAYER_SIZE);
  }

  /**
   * Checks if the object is located between the penalty areas for the moment.
   * 
   * @param y
   *          the Y-coordinate of the object
   * @param size
   *          the size of the object
   * @return true, if object is located in midfield, false otherwise
   * 
   * FIXME move to Formation
   */
  public final boolean inMidfield(final double y,
      final double size) {

    return (Math.abs(y) + size) < (SConf.getInstance().half_length - SConf
        .getInstance().penalty_length);
  }

  /**
   * This method returns a <code>LinkedList</code> of all known teammates that
   * are at a the given <code>angle</code> plus/minus <code>deviation</code>
   * and within the given <code>distance</code>. Note that if you give a
   * <code>deviation</code> of 180, you'll get all opponents within a circle
   * around the player with the radius <code>
   * maxDistance</code>
   * 
   * @param angle
   * @param position
   * @param deviation
   * @param maxDistance
   * @return LinkedList of <code>Player</code>s
   */
  public LinkedList<Player> getTeammates(final Vektor position,
      final double angle,
      final double deviation,
      final double maxDistance) {

    return this.players.getTeammates(position, angle, deviation, maxDistance);
  }

  /**
   * Calculates if the position denoted by the Vektor <code>pos</code> is
   * inside the field.
   * 
   * @param pos -
   *          Vektor
   * @return true if pos is inside the field.
   * 
   * FIXME move to SConf (static)
   */
  public final boolean inField(final Vektor pos) {

    if (pos.x > SConf.getInstance().half_width || pos.x < -SConf.getInstance().half_width || pos.y < -SConf
        .getInstance().half_length || pos.y > SConf.getInstance().half_length) {
      return false;
    }
    return true;
  }

  /**
   * sets a given position back into the field. this is used for the ball as due
   * to noise we might see it outside
   * 
   * @param pos -
   *          the position (this position gets changed !!!)
   * 
   * FIXME move to SConf (static)
   */
  public final void setIntoField(final Vektor pos) {

    if (pos.x > SConf.getInstance().half_width) {
      pos.x = SConf.getInstance().half_width - 0.01;
    }
    if (pos.x < -SConf.getInstance().half_width) {
      pos.x = -SConf.getInstance().half_width + 0.01;
    }
    if (pos.y > SConf.getInstance().half_length) {
      pos.y = SConf.getInstance().half_length - 0.01;
    }
    if (pos.y < -SConf.getInstance().half_length) {
      pos.y = -SConf.getInstance().half_length + 0.01;
    }
  }

  /**
   * isGoalie returns true if the player is a goalkeeper
   * 
   * @return boolean - true if player is a goalie
   */
  public final boolean isGoalie() {

    return this.myMaster.isGoalie();
  } // end of method isGoalie()

  /**
   * getBodyAngle returns the body angle of the player body = head - neck
   * 
   * @return double - the angle
   */
  public final double getBodyAngle() {

    return this.me.getBodyDir();
  } // end of getBodyAngle()

  /**
   * @param o -
   *          StaticObject
   * @return distance from the player to the StaticObject <code>o</code>.
   */
  public final double getDistance(final StaticObject o) {

    return this.getDistance(o.getPosition());
  }

  /**
   * @param v -
   *          Vektor
   * @return distance from the player to the Vektor <code>v</code>.
   */
  public final double getDistance(final Vektor v) {

    this.dummyVektor.copy(v);
    this.dummyVektor.subFromThis(this.me.getPosition());
    return this.dummyVektor.getLength();
  }

  /**
   * getOpponents returns an array with all opponents
   * 
   * @return Player[] - opponents array (REF!!)
   */
  public final Player[] getOpponents() {

    return this.players.getOpponents();
  }

  /**
   * getTeammates returns an array with all teammates
   * 
   * @return Vector - teammates (REF!!)
   */
  public final Player[] getTeammates() {

    return this.players.getTeammates();
  }

  /**
   * getAllPLayers an array with all players
   * 
   * @return Vector - all seen players (REF!!)
   */
  public final Player[] getAllPlayers() {

    return this.players.getAllPlayers();
  }

  /**
   * getExpectedPlayers returns an array with all expected players.
   * 
   * @return Returns the expectedPlayers (REF!!)
   */
  public final Player[] getExpectedPlayers() {

    return this.players.getExpectedPlayers();
  }

  /**
   * getNextPLayers returns all players as they are expected in the next cycle
   * 
   * @return Vector - all next players (REF!!)
   */
  public final Player[] getNextPlayers() {

    return this.players.getNextPlayers();
  }

  /**
   * getMyNextPosition return the position of the player in the next cycle,
   * assuming, that the player runs with current speed.
   * 
   * @return Vektor - absolute position
   */
  public final Vektor getMyNextPosition() {

    Vektor v = this.shortTermMemory.get(VEKTORS.MY_NEXT_POSITION);
    if (v == ShortTermMemory.VEKTOR_UNKNOWN) {
      v = this.getNextPosition(this.getSelfRef());
      this.shortTermMemory.set(VEKTORS.MY_NEXT_POSITION, v);
    }
    return v;
  }

  /**
   * getNextPosition(DynamicObject) returns the position of the given object in
   * the next cycle, assuming, that it's moving with the current speed.
   * 
   * @param obj
   * @return Vektor - absolute position
   * 
   * FIXME move to dynObject
   */
  public final Vektor getNextPosition(final StaticObject obj) {

    final Vektor nextPos = new Vektor();
    nextPos.copy(obj.getPosition());

    if (obj instanceof DynamicObject) {
      nextPos.addToThis(((DynamicObject) obj).getSpeed());
    }

    return nextPos;
  }

  /**
   * The Players number.
   * 
   * @return number of player
   */
  public final int getNumber() {
    
    if(!this.initialized)
      return -1;
    
    return this.getSelfRef().getNumber();
  }

  /**
   * getViewAngle returns the view angle of the player
   * 
   * @return double - width
   */
  public final double getViewWidth() {

    return this.me.getViewWidth();
  } // end of method getViewAngle()

  /**
   * <code>getOffsideY</code> Calculates the y value of the 'offside' line for
   * the given pos/team.
   * 
   * @param pos -
   *          Position
   * @param ourTeam -
   *          calculate offside for friends or opponents.
   * @return the y value of the other team's second last player's position. or
   *         pos.y if no second last player known.
   */
  public final double getOffsideY(final boolean ourTeam) {

    if (ourTeam) {
      return this.players.getTeamOffsideY();
    }
    else {
      return this.players.getOpponentOffsideY();
    }
  }

  /**
   * calculates the velocity of an dynamic object based on the information given
   * in the DynamicObjectInfo.
   * 
   * @param doi
   * @return Vektor - velocity
   * 
   * Status: UvA - complete
   */
  public void calculateVelocity(final DynamicObjectInfo doi,
      final Vektor velocity) {

    // no visual info about speed
    if (!doi.velocitySeen()) {
      velocity.setEmpty();
      return;
    }

    // see MT P.96 Method 1
    // TODO MT P.97 Method 3; less new value creation
    final double r = doi.getDistance();
    final double phi_rad = Math.toRadians(doi.getDirection());
    final double d_r = doi.getDistChange();
    final double d_phi_rad = Math.toRadians(doi.getDirChange());

    final double e_rx = Math.cos(phi_rad);
    final double e_ry = Math.sin(phi_rad);
    final double v_rx = d_r * e_rx - d_phi_rad * r * e_ry;
    final double v_ry = d_r * e_ry + d_phi_rad * r * e_rx;

    // swap x <-> y as we use different coordinates
    velocity.pointAt(v_ry, v_rx);
    // turn to get speed in abs. system
    velocity.rotate(this.me.getHeadDir());
    // add own speed
    velocity.addToThis(this.me.getSpeed());
  }

  /**
   * getClosestTeammateToBall returns the nearest friendly player to the ball in
   * the current cycle. The player himself (me) counts as a friendly player!
   * 
   * @return Player - friendly player with the shortest distance to the ball
   */
  public final Player getClosestTeammateToBall() {

    return this.players.getClosestPlayerTo(this.getTeammates(), this
        .getBallRef().getPosition(), true);
  }

  /**
   * getClosestTeammateToMe returns the nearest friendly player to me. The
   * player himself does not count as a friendly player. If there are no
   * teammates in the player's world model, the result will be null.
   * 
   * @return Player - teammate with shortest distance to the player himself or
   *         null if there are no teammates in the player's current world model
   */
  public final Player getClosestTeammateToMe() {

    return this.players.getClosestPlayerTo(this.getTeammates(), this.me
        .getPosition(), false);
  }

  /**
   * <code>getClosestOpponentTo</code>
   * 
   * @param obj
   * @return Player - opponent with shortest distance to the specified
   *         StaticObject <code>obj</code> or null if there are no opponents
   *         in the player's current world model
   * @see robocup.component.SoccerProphet#getClosestOpponentTo(robocup.component.worldobjects.StaticObject)
   */
  public final Player getClosestOpponentTo(final StaticObject obj) {

    return this.players.getClosestOpponentTo(obj.getPosition());
  }

  /**
   * getClosestOpponentTo
   * 
   * @param v
   * 
   * @return Player - opponent with shortest distance to the Vektor
   *         <code>v</code> or null if there are no opponents in the player's
   *         current world model
   */
  public final Player getClosestOpponentTo(final Vektor v) {

    return this.players.getClosestOpponentTo(v);
  }

  /**
   * This method returns a <code>LinkedList</code> of all known opponents that
   * are at a the given <code>angle</code> plus/minus <code>deviation</code>
   * and within the given <code>distance</code>. Note that if you give a
   * <code>deviation</code> of 180, you'll get all opponents within a circle
   * around the player with the radius <code>
   * maxDistance</code>
   * 
   * @param angle
   * @param deviation
   * @param maxDistance
   * @return LinkedList of <code>Player</code>s
   */
  public LinkedList<Player> getOpponents(final Vektor position,
      final double angle,
      final double deviation,
      final double maxDistance) {

    return this.players.getOpponents(position, angle, deviation, maxDistance);
  }

  /**
   * getFastestOpponentToBall
   * 
   * @return Player - enemy player who is predicted to be the fastest player to
   *         the ball or null if there are no opponents in the player's current
   *         world model
   */
  public final Player getFastestOpponentToBall() {

    // FIXME was ist, wenn mehrere Spieler gleich schnell sind
    // use prophet (rw)
    // if(this.ballCanBeKickedByOther())
    return this.players.getClosestPlayerTo(this.getOpponents(), this
        .getBallRef().getPosition(), false);
    // return this.players.getFastestOpponentToBall();
  }

  /**
   * Returns the current PlayMode/RefereeMessage.
   * 
   * @see robocup.component.ControllerNumbers
   * @return int - gameMode
   */
  public PLAY_MODE getPlayMode() {

    return this.playModeModel.getCurrentPlayMode();
  }

  /**
   * the play mode that was in place before the current playmode.
   * 
   * @return
   */
  public PLAY_MODE getPreviousPlaymode() {

    return this.playModeModel.getPreviousPlayMode();
  }

  /**
   * the playmode of the last cycle
   * 
   * @return
   */
  public PLAY_MODE getLastCyclesPlaymode() {

    return this.playModeModel.getLastCyclesPlayMode();
  }

  /**
   * the cycle when we got the last play mode info
   * 
   * @return
   */
  public int getLastPlayModeMessageCycle() {

    return this.playModeModel.getLastPlayModeMessageCycle();
  }

  /**
   * predicts the effect of an action on an object.
   * 
   * @param o -
   *          StaticObject
   * @param a -
   *          Action
   * @return object after action
   */
  public final StaticObject predictNext(final StaticObject o,
      final Action a) {

    // dynamic
    if (o instanceof DynamicObject) {
      // ball
      if (o instanceof Ball) {
        final Ball b = (Ball) o;
        return b.predictBallAfterAction(this.getSelfRef(), a);
      }
      // player
      if (o instanceof Player) {
        final Player p = (Player) o;
        return p.predictPlayerAfterAction(a);
      }
      System.out.println("Unknown moving object in Prophet.predictNext()");
    }
    // static
    return o;
  }

  /**
   * Tells if an object should be seen.
   * 
   * @param o
   * @return true if object in viewCone or visibleDistance
   */
  public boolean shouldBeSeen(final StaticObject o) {

    return this.me.shouldBeSeen(o);
  }

  /**
   * @return Returns the viFlag.
   */
  public final boolean isViBased() {

    return this.viBased;
  }

  /**
   * @return returns the body cycle
   */
  public final int getBodyCycle() {

    return this.bodyCycleCounter;
  }

  /**
   * Added for distinction of the players role
   */
  public int getPlayerType() {

    return this.me.getPlayerType();
  }

  /**
   * Added for distinction of the players role
   */
  public void setPlayerType(final int playerType) {

    this.me.setPlayerType(playerType);
  }

  /**
   * TeamFormation will be initialized only if state tactic is used.
   * 
   * @return Returns the teamFormation.
   */
//  public final int getTeamFormation() {
//
//    return this.teamFormation;
//  }

  /**
   * @param Sets
   *          the teamFormation.
   * @see robocup.tactics.state.StateEvaluation#initSelf()
   */
//  public void setTeamFormation(final int iTeamFormation) {
//
//    this.teamFormation = iTeamFormation;
//  }

  public PConf getMyPConf() {

    return this.getSelfRef().getPConf();
  }

  /**
   * a reference to myself. DON'T MESS AROUND WITH IT.
   * 
   * @return
   */
  public Player getSelfRef() {

    return this.self;
  }

  /**
   * @return Returns the moved.
   */
  public boolean hasMoved() {

    return this.getCycle() == this.lastMoveCycle;
  }

  /**
   * true if the ball is in the kickable range of another player.
   * 
   * @return
   */
  public boolean ballCanBeKickedByOther() {

    for (final Player p : this.getPlayersThatCanKick()) {
      if (!p.isMe()) {
        return true;
      }
    }
    return false;
  }

  /**
   * true if the ball is in the kickable range of an opponent.
   * 
   * @return
   */
  public boolean ballCanBeKickedByOpponent() {

    for (final Player p : this.getPlayersThatCanKick()) {
      if (!p.isFriend()) {
        return true;
      }
    }
    return false;
  }

  /**
   * a list of players that have the ball in their kickable range.
   * 
   * @return
   */
  public LinkedList<Player> getPlayersThatCanKick() {

    final LinkedList<Player> result = this.getPlayersThatCanKick(this.getBallRef(),
        this.getAllPlayers());
    return result;
  }

  /**
   * filters all players that can't kick the ball because they are to far.
   * 
   * @param b
   * @param player
   * @return
   */
  public LinkedList<Player> getPlayersThatCanKick(final Ball b,
      final Player[] player) {

    return this.players.getPlayersThatCanKick(b, player);
  }

  /**
   * This method checks if the next playmode is kickoff own (only available in
   * playmode before kickoff)
   * 
   * @return true, if the next playmode is kick-off own
   */
  public boolean isKickOffOwn() {

    // needed values
    final int halfTime = (int) (SConf.getInstance().HALF_TIME * 10);
    final int cycle = this.getCycle();
    final boolean teamEast = this.myMaster.isTeamEast();

    // is kick-off?
    if (cycle % halfTime == 0) {
      // teamEast && 2.KickOff -> true
      if (teamEast) {
        if (cycle % (2 * halfTime) != 0) {
          return true;
        }
        else {
          return false;
        }
      }
      else {
        // team West && 1.KickOff ->
        if (cycle % (2 * halfTime) == 0) {
          return true;
        }
        else {
          return false;
        }
      }
    }
    // FIXME
    // someone shot a goal!
    return true;
  }

  /**
   * a string containing the current cycle and the players number
   * 
   * @return
   */
  public String getCycleAndPlayerString() {

    return this.getCycle() + ". P:" + this.getNumber();
  }

  /**
   * the difference between the predicted pos and the calculated pos.
   * @return
   */
  public Vektor getSelfPositionEstimationError() {

    return this.me.getSelfPositionEstimationError();
  }

  /**
   * the opponents goalie.
   * 
   * @return the opponents goalie; null if unknown
   */
  public Player getOpponentsGoalie() {

    return this.players.getOpponentsGoalie();
  }

  /**
   * This method retrieves the name of the own team
   */
  public String getTeamName() {

    return this.myMaster.getTeamName();
  }

  public PConf[] getAvailablePconfs() {

    return this.configs;
  }

  /**
   * This method retrieves a player from our team or of the opponents team by
   * the given number. If <team> is true, our team is considered, else the
   * opponents team. The numbers must be from 1..11
   * 
   * @param num -
   *          the players number
   * @param team -
   *          our team?
   * @return the player
   */
  public Player getPlayerByNumber(final int num,
      final boolean team) {

    final Player[] players = team ? this.getTeammates() : this.getOpponents();
    for (final Player p : players) {
      if (p.getNumber() == num) {
        return p;
      }
    }
    return null;
  }

  /**
   * This method retrieves a player from the own team by giving the
   * corresponding (unique) role of the player!
   * 
   * @param role -
   *          the role of the requested player
   * @return the player himself
   */
  public Player getPlayerByRole(final ROLE role) {

    for (final Player p : this.getTeammates()) {
      if (p.getDefaultRole() == role) {
        return p;
      }
    }
    return null;
  }

  /**
   * Retrieves the message-factory
   */
  public MessageFactory getMessageFactory() {

    return this.mf;
  }

  /**
   * @return
   */
  public Player getExpectedSelf() {

    return this.me.getPredictedMe();
  }

  /**
   * @return
   */
  public Player getLastMe() {

    return this.me.getLastMe();
  }

  public MESSAGES getLastHeardMessageType() {

    return this.longTermMemory.getHearedMessageType(0);
  }

  /**
   * 
   * @return goals(we) - goals(other)
   */
  public int getGoalDiff() {

    return this.goalsTeam - this.goalsOther;
  }

  /**
   * @param play_on
   */
  public void correctPlayMode(final PLAY_MODE play_on) {

    this.playModeModel.correctPlayMode(play_on);

  }

  /**
   * 
   * @return true if we collided with something
   */
  public boolean collided() {

    return this.collisionModel.collided();
  }

  /**
   * returns predefined sorted player arrays.
   * 
   * @param sorted -
   *          the sort mode and type
   * @return an array containing the sorted player references
   */
  public Player[] getPlayersSortedBy(final PlayersModel.SORTED sorted) {

    return this.players.getPlayersSortedBy(sorted, null);
  }

  /**
   * returns predefined sorted player arrays.
   * 
   * @param sorted -
   *          the sort mode and type
   * @param position -
   *          the position needed for sorting (i.e. by distance or angle)
   * @return an array containing the sorted player references
   */
  public Player[] getPlayersSortedBy(final PlayersModel.SORTED sorted,
      final Vektor position) {

    return this.players.getPlayersSortedBy(sorted, position);
  }

  /**
   * @see PlayersModel.sortPlayersBy()
   */
  public void sortPlayersBy(final LinkedList<Player> players,
      final DIMENSION dim,
      final ORDER order,
      final Vektor pos) {

    this.players.sortPlayersBy(players, dim, order, pos);
  }

  /**
   * gets a players number based on a role
   * 
   * @param role -
   *          the players role
   * @return the players number
   */
  public int getPlayerNumberByRole(final int role) {

    return this.roleModel.getNumberForRole(role);
  }

  /**
   * returns the expected player to a given role
   * 
   * @param role -
   *          the role
   * @return the expected player
   */
  public Player getExpectedPlayerByRole(final int role) {

    return this.getExpectedPlayers()[this.roleModel.getNumberForRole(role) - 1];
  }

  /**
   * true if both players are neighbours (based on their role)
   * 
   * @param p1
   * @param p2
   * @return
   */
  public boolean isNeighbour(final Player p1,
      final Player p2) {

    return this.players.isNeighbour(p1, p2);
  }

  /**
   * true if both players are neighbours (based on their role)
   * 
   * @param p1
   * @param p2
   * @return
   */
  public boolean isDirectNeighbour(final Player p1,
      final Player p2) {

    return this.players.isDirectNeighbour(p1, p2);
  }

  /**
   * gets the side were most opponents are (left, right, none).
   * 
   * @return
   */
  public DOMINATED_SIDE getDominatedSide() {

    return this.players.getDominatedSide();
  }

  /**
   * @return Returns the opponentTeamName.
   */
  public String getOpponentTeamName() {

    return this.opponentTeamName;
  }

  /**
   * @param opponentTeamName
   *          The opponentTeamName to set.
   */
  public void setOpponentTeamName(final String opponentTeamName) {

    this.opponentTeamName = opponentTeamName;
  }

  public CLangModel getClangModel() {

    return this.clangModel;
  }

  public int getGoalsOther() {

    return this.goalsOther;
  }

  public int getGoalsTeam() {

    return this.goalsTeam;
  }

  public void resetFilterAfterCollisionWithBall() {

    this.ball.resetFilterAfterCollisionWithBall();
    this.me.resetFilterAfterCollisionWithBall();
  }
  
  public int getNumberViewSlicesExp() {
    return viewModel.getNumberSlicesExp();
  }
  
  public double[] getViewSliceHistory() {
    return viewModel.getSliceHistory();
  }
  
  public double[] getEstimatedViewSliceAreas() {
    return viewModel.getEstimatedSliceAreas();
  }

  public int getViewSliceContainingPoint(final Vektor myNextPosition,
      final Vektor vektor) {

    return viewModel.getSliceContainingPoint(myNextPosition, vektor);
  }

  public double getViewSliceWidth() {

    return viewModel.getSliceWidth();
  }

  public int getNumberViewSlices() {

    return viewModel.getNumberSlices();
  }

  public int getViewSliceDiff(final int j,
      final int objectSlice) {

    return viewModel.getSliceDiff(j, objectSlice);
  }

  public double getViewSliceLeftAngle(final int i) {

    return viewModel.getSliceLeftAngle(i);
  }

  public double getViewSliceRightAngle(final int i) {

    return viewModel.getSliceRightAngle(i);
  }

  public double getViewSliceMeridian(final int i) {

    return viewModel.getSliceMeridian(i);
  }

  public int getLeftViewSliceNeighbour(final int slice) {

    return viewModel.getLeftSliceNeighbour(slice);
  }

  public int getRightViewSliceNeighbour(final int slice) {

    return viewModel.getRightSliceNeighbour(slice);
  }

  public double overlapAreaPercentage(final int i,
      final double leftBorder,
      final double rightBorder) {

    return viewModel.overlapAreaPercentage(i, leftBorder, rightBorder);
  }

  public int getViewSliceContainingAbsAngle(final double angle) {

    return viewModel.getSliceContainingAbsAngle(angle);
  }
  
  public double getViewSliceAreaMax() {
    return viewModel.getAreaMax();
  }

  
  /**
   * The initialized.
   *
   * @return the initialized
   */
  public boolean isInitialized() {
  
    return this.initialized;
  }
  
  public Vektor getOxsyKickInPosition(int num) {
	  double maxY = this.getBallRef().getPosition().y + 7;
	  maxY = Math.min(maxY, 40);
	  double x = 0;
	  if (this.getBallRef().getPosition().x > 0) {
		  if (num == 9) {
			  x = 10;
		  } else if (num == 10) {
			  x = 17;
		  } else if (num == 11) {
			  x = 25;
		  }
	  } else {
		  if (num == 9) {
			  x = -25;
		  } else if (num == 10) {
			  x = -17;
		  } else if (num == 11) {
			  x = -10;
		  }
	  }
	  return new Vektor(Vektor.XY, x,maxY);
  }
  
  /**
   * 
   * @param num
   * @return
   */
  public boolean isOxsyKickInPosition() {
	  for (int i = 9; i < 12; i++) {
		  Player p = this.getPlayerByNumber(i, true);
		  Vektor v = this.getOxsyKickInPosition(i);
		  if (p.getDistance(v) > 3)
			  return false;
	  }
	  return true;
  }
  
  /**
   * Retrieves the distance that a player (only teammates) is 
   * beyond all opponents. This is used for OxsyKickInState. 
   */
  public double beyondOpponentsDist(int numOfTeammate) {
	Player p = this.getPlayerByNumber(numOfTeammate, true);
	double maxY = 0;
	for (Player opp : this.getOpponents()) {
		if (opp.exists() && !opp.isGoalie()) {
			maxY = Math.max(opp.getPosition().y, maxY);
		}
	}
	if (p != null) {
		return (p.getPosition().y - maxY);
	} else {
		return -100;
	}
  }

} // end of class WorldModel
